package felix.rule.service.actors.chain;


import felix.actor.core.TActorCtx;
import felix.actor.core.TActorRef;
import felix.rule.api.TNode;
import felix.rule.api.TNodeConfiguration;
import felix.rule.data.msg.RuleNode;
import felix.rule.data.msg.RuleNodeException;
import felix.rule.data.msg.RuleNodeInfo;
import felix.rule.data.plugin.ComponentLifecycleState;
import felix.rule.service.actors.ActorSystemContext;
import felix.rule.service.actors.sharde.ComponentMsgProcessor;

public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor {

    private final String ruleChainName;
    private final TActorRef self;
    private RuleNode ruleNode;
    private TNode tNode;
    private DefaultTContext defaultCtx;
    private RuleNodeInfo info;

    RuleNodeActorMessageProcessor(String ruleId, String ruleChainName, String ruleNodeId, ActorSystemContext systemContext
            , TActorRef parent, TActorRef self) {
        super(systemContext, ruleId, ruleNodeId);
        this.ruleChainName = ruleChainName;
        this.self = self;
        this.ruleNode = systemContext.getRuleChainService().findRuleNodeById(id);
        this.defaultCtx = new DefaultTContext(systemContext, new RuleNodeCtx(ruleId, parent, self, ruleNode));
        this.info = new RuleNodeInfo(ruleNodeId, ruleChainName, ruleNode != null ? ruleNode.getName() : "Unknown");
    }

    @Override
    public void start(TActorCtx context) throws Exception {
        tNode = initComponent(ruleNode);
        if (tNode != null) {
            state = ComponentLifecycleState.ACTIVE;
        }
    }

    @Override
    public void onUpdate(TActorCtx context) throws Exception {
        RuleNode newRuleNode = systemContext.getRuleChainService().findRuleNodeById(id);
        this.info = new RuleNodeInfo(id, ruleChainName, newRuleNode != null ? newRuleNode.getName() : "Unknown");
        boolean restartRequired = state != ComponentLifecycleState.ACTIVE ||
                newRuleNode != null && !(ruleNode.getType().equals(newRuleNode.getType())
                        && ruleNode.getConfiguration().equals(newRuleNode.getConfiguration()));
        this.ruleNode = newRuleNode;
        this.defaultCtx.updateSelf(newRuleNode);
        if (restartRequired) {
            if (tNode != null) {
                tNode.destroy();
            }
            start(context);
        }
    }

    @Override
    public void stop(TActorCtx context) {
        if (tNode != null) {
            tNode.destroy();
            state = ComponentLifecycleState.SUSPENDED;
        }
    }

    public void onRuleToSelfMsg(RuleNodeToSelfMsg msg) throws Exception {
        checkActive(msg.getMsg());
        if (ruleNode.isDebugMode()) {
            systemContext.persistDebugInput(ruleId, id, msg.getMsg(), "Self");
        }
        try {
            tNode.onMsg(defaultCtx, msg.getMsg());
        } catch (Exception e) {
            defaultCtx.tellFailure(msg.getMsg(), e);
        }
    }

    void onRuleChainToRuleNodeMsg(RuleChainToRuleNodeMsg msg) throws Exception {
        msg.getMsg().getCallback().visit(info);
        checkActive(msg.getMsg());
        if (ruleNode.isDebugMode()) {
            systemContext.persistDebugInput(ruleId, id, msg.getMsg(), msg.getFromRelationType());
        }
        try {
            tNode.onMsg(msg.getCtx(), msg.getMsg());
        } catch (Exception e) {
            msg.getCtx().tellFailure(msg.getMsg(), e);
        }
    }

    @Override
    public String getComponentName() {
        return ruleNode.getName();
    }

    private TNode initComponent(RuleNode ruleNode) throws Exception {
        TNode tNode = null;
        if (ruleNode != null) {
            Class<?> componentClazz = Class.forName(ruleNode.getType());
            tNode = (TNode) (componentClazz.newInstance());
            tNode.init(defaultCtx, new TNodeConfiguration(ruleNode.getConfiguration()));
        }
        return tNode;
    }

    @Override
    protected RuleNodeException getInactiveException() {
        return new RuleNodeException("Rule Node is not active! Failed to initialize.", ruleChainName, ruleNode);
    }
}
